#pragma once

#include <iostream>
#include <vector>
#include <semaphore.h>
#include <pthread.h>
#include "Sem.hpp"
#include "Mutex.hpp"

using namespace std;

namespace RingBufferModule
{
    using namespace SemModule;
    using namespace LockModule;

    // RingQueue
    template<typename T>
    class RingBuffer
    {
    public:
        RingBuffer(int cap)
            :_ring(cap)
            ,_cap(cap)
            ,_p_step(0)
            ,_c_step(0)
            ,_datasem(0)
            ,_spacesem(cap)
        {}

        void Equeue(const T& in)
        {
            // 生产者,关心的是有无空间让我生产
            _spacesem.P();
            // 为什么要先进行P操作再加锁？假设有一个线程抢到锁了，其余的线程在等待时间可以顺便先预定完空间，这是一个并发的操作；而如果先加锁再申请空间，那么一个线程拿到锁了，还是要进行P操作，这就不是一个并发操作，所以先预定位置再加锁，会提高效率，消费者也是同样的道理
            // 使用大括号划分临界区，大括号开始会加锁，出了大括号就会解锁
            {
                LockGuard lock_guard(_p_lock);
                _ring[_p_step] = in;  // 生产完毕
                _p_step++;
                _p_step %= _cap;  // 维持环形特性
            }

            _datasem.V();
        }

        void Pop(T* out)
        {
            // 消费者，关心的是有没有数据让我消费
            _datasem.P();  // 先预定一个数据

            {
                LockGuard lock_guard(_c_lock);
                *out = _ring[_c_step];
                _c_step++;
                _c_step %= _cap;
            }

            _spacesem.V();
        }

        ~RingBuffer()
        {}
    private:
        vector<T> _ring;  // 环，临界资源
        int _cap;  // 总容量
        int _p_step;  // 生产者位置
        int _c_step;  // 消费者位置

        Sem _datasem;  // 数据信号量
        Sem _spacesem;  // 空间信号量

        Mutex _p_lock;  // 生产者与生产者之间的锁
        Mutex _c_lock;  // 消费者和消费者之间的锁
    };
}